#!/usr/bin/env python3
# fileencoding=utf-8

import sys, readline, os
import mb3
import charset
import code, traceback
from datetime import datetime
import traceback

ver = 0.3

# 完整的版本号
lastModified = datetime.fromtimestamp(os.path.getmtime(sys.argv[0]))
version = '%.1f.%d%02d%02d' % (ver, lastModified.year, lastModified.month, lastModified.day)

def bold(string):
  return '\x1b[1m'+string+'\x1b[0m'

def underline(string):
  return '\x1b[4m'+string+'\x1b[0m'

# 完整显示一条目时的格式；会在载入码表后修改
format = '{code:%d}\t{hz}{hit:>5}{index:>8}'
cmds = {
    'quit':      ('quit', 'q', 'exit'),
    'help':      ('help', 'h', 'usage'),
    'ver':       ('ver',),

    'info':      ('info',),
    'find':      ('find', 'f'),
    'verbose':   ('verbose', 'v'),
    'range':     ('range',),
    'size':      ('size',),
    'similar':   ('similar', 's'),

    'set':       ('set',),
    'insert':    ('insert', 'a', 'add', 'ins'),
    'delete':    ('delete', 'd', 'del'),
    'strictdel': ('strictdel', 'sd', 'sdel'),
    'delcode':   ('delcode', 'dc'),

    'write':     ('write', 'w', 'saveas'),
    'print':     ('print', 'p', 'mb2txt'),
    'wq':        ('wq',),
    'console':   ('console',),
    }

helps = {
    # 命令名     参数  简要说明     详细解释
    'quit':      ('', '退出本程序', '如果需要，退出前会询问是否保存。但本程序并不能确保可靠，所以还是请记得退出前要保存哦。'),
    'help':      ('[CMD]', '显示说明', 'CMD 是不带参数输入 help 命令时显示在第一个的词。在格式说明中，[X] 表示 X 是可选的，... 表示前边的项目可重复多次，| 表示“或者”。'),
    'ver':       ('', '显示本程序的版本号', ''),

    'info':      ('', '显示码表的基本信息', ''),
    'find':      ('WHAT...', '查询编码或词组', 'WHAT 是要查询的编码或者词组。如果 WHAT 符合当前码表的编码，那么作为编码查询，否则作为词组查询。'),
    'verbose':   ('WHAT...', '查询编码的详细信息', '查询编码 WHAT 的信息，包括它对应的词组，词频信息，以及是否有拼音标记（编码前以“@”标出）。'),
    'range':     ('FROM TO', '查询某个编码范围内的汉字', 'FROM 和 TO 分别是范围的起始编码和终止编码。'),
    'size':      ('', '显示当前码表中词条数目', ''),
    'similar':   ('WHAT','查询编码相似条目', '查询与所给 编码 或 词组 的编码相似的编码及对应的词组'),

    'set':       ('CODE HZ [HIT [INDEX [ISPY]]]', '与 C++ 版的兼容，添加或更新条目', '添加或者更新（如果已经存在）编码为 CODE、词组为 HZ 的词频，并设置拼音标记为 ISPY。ISPY 为 true（不区分大小写）时表示是拼音，其它情况不是。'),
    'insert':    ('[CODE:]HZ[:HIT[:INDEX[:ISPY]]]|HZ ...', '添加或更新条目', '如果没有给出 CODE（编码），将依据码表的组词规则自动生成。ISPY 如果给出，当它为 true（不区分大小写）时表示是拼音，其它情况不是。'),
    'delete':    ('HZ...', '删除词组', '将删除所有给出词组，不管它有几个编码。删除单字时请谨慎考虑。'),
    'strictdel': ('CODE HZ', '按编码和词组删除项目', ''),
    'delcode':   ('CODE...', '删除某个编码下的所有项目。', '记得先用 f 或者 v 看看会删掉哪些东西哦。'),

    'write':     ('FILE', '保存到文件', '如果没有给出 FILE，写到当前文件。'),
    'wq':        ('', '保存并退出', ''),
    'print':     ('[FILE [保留词频？ [字符编码]]]', '导出到文本文件', '保留词频 可为 "True" 或 "False"。字符编码默认为 "utf-8"。当只给出文件名（FILE）时，此功能与 mb2txt 基本相同。'),
    'console':   ('', '启动 Python 控制台。仅供会 Python 者使用。', ''),
    }

# 用于给帮助排序
helplist = ['quit', 'help', 'ver', '',
    'info', 'find', 'verbose', 'range', 'size', 'similar', '',
    'insert', 'set', 'delete', 'strictdel', 'delcode', '',
    'write', 'print', 'wq', 'console',
    ]

def main(file):
  def find(what, compact=False):
    for i in what:
      if not m.maybeCode(i):
        # 找词组
        x = m.search(hz=i)
        if not x:
          print('（没有找到词 %s。）' % i)
          continue
        x = [m[i].code for i in x]
      else:
        # 找编码
        x = m.gethz(code=i)
        if not x:
          print('（没有找到编码 %s。）' % i)
          continue
      if compact:
        print(i.ljust(m.码长)+'\t', end='')
      else:
        print(i+':\n\t', end='')
      [print(j+', ', end='') for j in x]
      print('\b\b\x1b[K') # 删除最后的逗号和空格

  def verbose(what):
    for i in what:
      if not m.maybeCode(i):
        print('（%s 不是一个合法的编码。）' % i)
      else:
        # 找编码
        x = m.getbycode(code=i)
        if x:
          for j in x:
            recprint(i, j)
        else:
          print("（没有找到编码 `%s'。）" % i)

  def dorange(what):
    if len(what) < 2:
      print('参数过少。')
      return
    between = range(m.getpos(what[0]), m.getpos(what[1]))
    lastcode = ''
    for i in between:
      if m[i].code == lastcode:
        print('%s, ' % m[i].hz, end='')
      else:
        if lastcode:
          print('\b\b\x1b[K') # 删除最后的逗号和空格
        lastcode = m[i].code
        print(lastcode.ljust(m.码长)+'\t', end='')
        print('%s, ' % m[i].hz, end='')
    print('\b\b\x1b[K') # 删除最后的逗号和空格
    # 最后的编码也要包含
    x = m.gethz(code=what[1])
    if x:
      print(what[1].ljust(m.码长)+'\t', end='')
      [print(j+', ', end='') for j in x]
      print('\b\b\x1b[K') # 删除最后的逗号和空格
    if len(what) > 2:
      print('警告：多余的参数被忽略了。')

  def getsimilar(what):
    what = what[0]
    if m.maybeCode(what):
      s = m.getsimilar(what)
      find(s, True)
    else:
      for i in m.search(what):
        code = m[i].code
        print('对于编码 %s：' % code)
        s = m.getsimilar(code)
        find(s, True)

  def insert(what):
    for i in what:
      if i.find(':') != -1:
        # 指定编码
        ii = i.split(':')
        if m.maybeCode(i[0]): shift = 0
        else: shift = -1
        try:
          ii[2+shift] = int(ii[2+shift])
          ii[3+shift] = int(ii[3+shift])
          if ii[4+shift].lower() == 'true':
            ii[4+shift] = True
          else:
            ii[4+shift] = False
        except IndexError:
          pass
        except ValueError:
          print(i, '格式错误。正确的格式为 [编码:]汉字[:hit[:index[:拼音(True|False)]]]', sep='\t')
          continue
        try:
          m.get(mb3.Record(ii[0], ii[1]))
          print('条目 %s 已更新。' % i)
        except m.RecordNotExist:
          print('条目 %s 已加入。' % i)
        # 时间顺序有点不对，但应该不会出问题吧
        m.set(*ii)
      else:
        try:
          ii = m.autoCode(i)
          m.insert(ii, i)
          print('条目 [%s:%s] 已加入。' % (ii, i))
        except m.autoCodeError as e:
          print(i, e.value, sep=':\t')
        except m.RecordExists as e:
          print('错误：', str(e))

  def set(what):
    '''兼容 C++ 版的 set'''
    try:
      what[2] = int(what[2])
      what[3] = int(what[3])
      if what[4].lower() == 'true':
        what[4] = True
      else:
        what[4] = False
    except IndexError:
      pass
    except ValueError:
      print('参数值有误。')
      return
    try:
      m.set(*what)
      print(what, '已加入或者更新。')
    except TypeError:
      print('参数过少。')

  def delete(what):
    count = 0
    for i in what:
      count += m.delete(hz=i)
    print('%d 条目已删除。' % count)

  def sdelete(what):
    try:
      if m.delete(what[0], what[1]):
        print('条目 [%s:%s] 已删除。' % (what[0], what[1]))
      else:
        print('条目 [%s:%s] 未删除，因为它并不存在。' % (what[0], what[1]))
    except IndexError:
      print('错误：需要给出 编码 和 词组。')

  def cdelete(what):
    count = 0
    for i in what:
      count += m.delete(code=i)
    print('%d 条目已删除。' % count)

  def saveas(what):
    '''保存文件，另命名或者到原文件'''
    if what:
      if os.path.isfile(what[0]):
        if input('文件已存在。要覆盖吗？') not in ('y', 'yes',
            'Yes', '是', '是的', '覆盖'):
          print('因为所给文件已存在而未保存。')
          return
      try:
        print('正在保存文件，请等待几秒钟...')
        m.write(what[0])
        print('文件已保存到 %s。' % what[0])
      except IOError as e:
        print('\r出错了！', end='', file=sys.stderr)
        if e.errno == 13:
          print('文件', what[0], '没有写权限。', file=sys.stderr)
        elif e.errno == 21:
          print(what[0], '是个目录（但我要的是文件名）。', file=sys.stderr)
        else:
          print('看看这是什么错误——错误号', e.errno, '；错误信息', e.strerror, file=sys.stderr)
    else:
      if not m.modified:
        if input('文件似乎未修改，仍要保存吗？') not in ('y', 'yes',
            'Yes', '是', '是的', '保存'):
          print('因为文件未修改而未保存。')
          return
      print('正在保存文件，请等待几秒钟...')
      m.save()
      print('文件已保存到原文件。')

  def doprint(what):
    '''导出到文本文件'''
    if what:
      if os.path.isfile(what[0]):
        if input('文件已存在。要覆盖吗？') not in ('y', 'yes',
            'Yes', '是', '是的', '覆盖'):
          print('因为所给文件已存在而未导出。')
          return
      try:
        print('正在导出码表，请等待几秒钟...')
        if len(what) > 1:
          what[1] = True if what[1].lower() == 'true' else False
        m.print(*what)
        print('码表已导出到 %s。' % what[0])
      except IOError as e:
        print('\r出错了！', end='', file=sys.stderr)
        if e.errno == 13:
          print('文件', what[0], '没有写权限。', file=sys.stderr)
        elif e.errno == 21:
          print(what[0], '是个目录（但我要的是文件名）。', file=sys.stderr)
        else:
          print('看看这是什么错误——错误号', e.errno, '；错误信息', e.strerror, file=sys.stderr)
      except LookupError:
        print('\r出错了！', end='', file=sys.stderr)
        print('编码 %s 未知。' % what[2])
    else:
      m.print(编码='utf-8')

  def checkexit():
    '''退出前检查是否已修改但未保存'''
    if m.modified:
      try:
        if input('文件已修改但尚未保存。仍然要退出吗？') not in ('y', 'yes',
            'Yes', '是', '是的', '退出'):
          return False
      except EOFError:
        print("已强制退出。", file=sys.stderr)
        sys.exit(1)
    return True

  def console():
    mbName = os.path.splitext(os.path.basename(file))[0]
    code.interact('''已经进入 Python 控制台。请对你的码表文件执行你想要的操作。
你的码表对象是 {mbName}。你可以键入 help({mbName}) 查看如何使用。'''.format(mbName=mbName), local={
          mbName: m,
          'Record': mb3.Record,
        })
    print('已退出 Python 控制台。')

  def mainloop():
    '''命令循环'''
    while True:
      try:
        cmd = input('mb> ')
        cmd = cmd.split()
        if not cmd:
          continue
        if cmd[0] in cmds['help']:
          usage(cmd[1:])
        elif cmd[0] in cmds['ver']:
          print('小企鹅输入法码表操作工具 v%s' % version)
        elif cmd[0] in cmds['info']:
          print(m)
        elif cmd[0] in cmds['find']:
          find(cmd[1:])
        elif cmd[0] in cmds['verbose']:
          verbose(cmd[1:])
        elif cmd[0] in cmds['range']:
          dorange(cmd[1:])
        elif cmd[0] in cmds['size']:
          print('此码表现有 %d 个条目。' % m.size())
        elif cmd[0] in cmds['similar']:
          getsimilar(cmd[1:])
        elif cmd[0] in cmds['insert']:
          # *不*兼容 C++ 版的 insert
          insert(cmd[1:])
        elif cmd[0] in cmds['set']:
          # 兼容 C++ 版的 set
          set(cmd[1:])
        elif cmd[0] in cmds['delete']:
          delete(cmd[1:])
        elif cmd[0] in cmds['strictdel']:
          sdelete(cmd[1:])
        elif cmd[0] in cmds['delcode']:
          cdelete(cmd[1:])
        elif cmd[0] in cmds['wq']:
          print('正在保存文件，请等待几秒钟...')
          m.save()
          print('文件已保存。')
          break
        elif cmd[0] in cmds['write']:
          saveas(cmd[1:])
        elif cmd[0] in cmds['print']:
          doprint(cmd[1:])
        elif cmd[0] in cmds['quit']:
          if checkexit():
            break
        elif cmd[0] in cmds['console']:
          console()
        else:
          print('（语法错误。）')
        print()
      except EOFError: # 退出
        print() # 加个换行
        if checkexit():
          break
      except KeyboardInterrupt:
        print('\n按 Ctrl-D 退出。')
      except:
        traceback.print_exc()
    print('谢谢使用！')
    sys.exit(0)

  def loadfile():
    '''此函数用作折叠标记，并无实际用途'''
    pass

  print('\t欢迎使用 \x1b[1;35m小企鹅输入法码表操作工具（Python版）\x1b[0m%s 版' % version)

  print('载入文件中，请稍候几秒钟...')
  try:
    m = mb3.mbTable(file)
  except IOError as e:
    print('\r出错了！', end='', file=sys.stderr)
    if e.errno == 2:
      print('文件', file, '不存在。', file=sys.stderr)
    elif e.errno == 13:
      print('文件', file, '没有读权限。', file=sys.stderr)
    elif e.errno == 21:
      print(file, '是个目录（但我要的是文件）。', file=sys.stderr)
    else:
      print('看看这是什么错误——错误号', e.errno, '；错误信息', e.strerror, file=sys.stderr)
    sys.exit(1)
  except mb3.struct.error:
    # 不是码表文件
    print('载入文件', file, '时出错了——你确定它是小企鹅输入法的码表文件吗？',
        file=sys.stderr)
    sys.exit(2)

  print('\x1b[1A\x1b[K文件', file, '载入完毕。help - 帮助， quit - 退出。')

  # 确定显示的格式
  global format
  format %= m.码长
  mainloop()

def recprint(code, item):
  '''显示条目的详细信息'''

  code = '@' + item.code if item.ispy else item.code
  print(format.format(code=code, hz=charset.CJK_align(item.hz, 15), hit=item.hit, index=item.index))

def usage(what):
  '''帮助'''
  print('用法说明：')
  f = '\t{cmd:{width}}{text}'
  ff = '{cmd} {format:{fwidth}} {short}\n\t{long}'
  ffs = '{cmd} {format:{fwidth}} {short}'
  if not what:
    for i in helplist:
      if i:
        print(f.format(cmd=str(cmds[i]).strip('(), ').replace("'", ''),
          width=30, text=helps[i][1]))
      else:
        print()
  else:
    try:
      t = list(helps[what[0]])
      what[0] = bold(what[0])
      t[0] = underline(t[0])
      if t[2]:
        print(ff.format(cmd=what[0], format=t[0], fwidth=15,
          short=t[1], long=t[2]))
      else:
        print(ffs.format(cmd=what[0], format=t[0], fwidth=15, short=t[1]))
    except KeyError:
      print('抱歉，没有对 %s 的说明。' % what[0])

if __name__ == '__main__':
  if len(sys.argv) == 2:
    main(sys.argv[1])
  else:
    print("用法： %s 码表文件" % os.path.basename(sys.argv[0]), file=sys.stderr)
